package cc.rengu.igas.opms.core.service.trans.quickpay;

import cc.rengu.igas.opms.common.constant.OpmsParamConstant;
import cc.rengu.igas.opms.common.enums.ChannelTypeEnum;
import cc.rengu.igas.opms.common.enums.RespCodeEnum;
import cc.rengu.igas.opms.common.enums.SignTokenTypeEnum;
import cc.rengu.igas.opms.common.util.OpmsUpacSecurityUtil;
import cc.rengu.igas.opms.common.util.ParameterUtil;
import cc.rengu.igas.opms.core.service.base.ManageService;
import cc.rengu.igas.opms.facade.request.CardSignAcqRequest;
import cc.rengu.igas.opms.facade.response.CardSignAcqResponse;
import cc.rengu.igas.share.common.constant.UserSessionConstant;
import cc.rengu.igas.share.common.enums.SignTypeEnum;
import cc.rengu.igas.share.core.model.BankCodeInfo;
import cc.rengu.igas.share.core.model.UserInfo;
import cc.rengu.igas.share.core.realize.BompService;
import cc.rengu.igas.share.core.realize.UserService;
import cc.rengu.igas.share.core.realize.impl.BompServiceImpl;
import cc.rengu.igas.share.core.realize.impl.UserServiceImpl;
import cc.rengu.oltp.service.common.constant.AppParamConstant;
import cc.rengu.oltp.service.common.constant.TreeNodeConstant;
import cc.rengu.oltp.service.common.dao.CertInfoMapper;
import cc.rengu.oltp.service.common.dao.DstChannelCfgMapper;
import cc.rengu.oltp.service.common.dao.SecPlanInfoMapper;
import cc.rengu.oltp.service.common.dao.SignAcctInfoMapper;
import cc.rengu.oltp.service.common.dao.impl.CertInfoMapperImpl;
import cc.rengu.oltp.service.common.dao.impl.DstChannelCfgMapperImpl;
import cc.rengu.oltp.service.common.dao.impl.SecPlanInfoMapperImpl;
import cc.rengu.oltp.service.common.dao.impl.SignAcctInfoMapperImpl;
import cc.rengu.oltp.service.common.entity.*;
import cc.rengu.oltp.service.common.enums.AcctTypeEnum;
import cc.rengu.oltp.service.common.enums.ChannelEnum;
import cc.rengu.oltp.service.model.BizException;
import cc.rengu.oltp.service.model.BizResponse;
import cc.rengu.oltp.service.realize.SysParamService;
import cc.rengu.oltp.service.realize.impl.BinInfoServiceImpl;
import cc.rengu.oltp.service.realize.impl.SysParamServiceImpl;
import cc.rengu.oltp.utility.util.*;
import cc.rengu.utility.cache.UnifiedCache;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.codec.binary.Base64;

import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 银行卡全渠道签约/重新签约/修改用户手机号
 *
 * @author keyi
 * @date 2020/4/28.
 */
public class CardSignAcqService extends ManageService {

    @Override
    protected Object bizBeforeCust() throws Exception {
        CardSignAcqRequest cardSignAcqRequest = new CardSignAcqRequest();
        ConvertUtil.convertOutput(cardSignAcqRequest);
        //去掉参数的空格，并对金额进行格式化
        ParameterUtil.paramertTrimAndFormatAmt(cardSignAcqRequest);
        return cardSignAcqRequest;
    }

    @Override
    protected BizResponse bizProcessCust(Object request) throws Exception {
        XmlTreeUtil xmlTreeUtil = new XmlTreeUtil();
        String sysSeqNum = xmlTreeUtil.getXmlTreeStringValue(TreeNodeConstant.SYS_SEQ_NUM);
        CardSignAcqRequest cardSignAcqRequest = (CardSignAcqRequest) request;
        String instId = cardSignAcqRequest.getHeader().getInstId();
        String bizType = cardSignAcqRequest.getHeader().getBizType();
        String userId = cardSignAcqRequest.getUserId();
        String platMchntNo = cardSignAcqRequest.getPlatMchntNo();
        String mchntNo = cardSignAcqRequest.getMchntNo();
        String payAcctNo = cardSignAcqRequest.getPayAcctNo();
        String certType = cardSignAcqRequest.getCertType();
        String certNo = cardSignAcqRequest.getCertNo();

        /* Step1：获取用户信息 */
        UserService userService = new UserServiceImpl();
        UserInfo userInfo = userService.getUserInfo(instId, userId, cardSignAcqRequest.getHeader().getSession());
        if (null == userInfo) {
            rglog.error("获取用户信息失败!");
            throw new BizException(RespCodeEnum.USER_NOT_LOGIN_OR_SESSION_IS_EXPIRED.getRespCode(), RespCodeEnum.USER_NOT_LOGIN_OR_SESSION_IS_EXPIRED.getRespDesc());
        }
        String signInst = userService.getUserSignInst();
        String signType = userService.getUserSignType();

        /* Step2：校验三要素校验接口是否调用成功,防止越过三要素交易直接进行签约 */
        String authenticationFinishFlag = userService.getUserSessionValue(UserSessionConstant.AUTHENTICATION_FINISH_FLAG);
        if (!AppParamConstant.YES.equals(authenticationFinishFlag)) {
            rglog.info("交易流程跳跃，三要素校验接口未完成.<{}>", authenticationFinishFlag);
            throw new BizException(RespCodeEnum.NOT_SURP_TRANS_TYPE_ERROR.getRespCode(), RespCodeEnum.NOT_SURP_TRANS_TYPE_ERROR.getRespDesc());
        }

        /* Step3:获取银联全渠道商户相关信息 */
        JSONObject upacMchntInfo = getUpacMchntInfo(instId, platMchntNo, mchntNo, signType);
        String prodType = upacMchntInfo.getString(OpmsParamConstant.PROD_TYPE);

        /* Step4:检查签约信息 */
        SignAcctInfoMapper signAcctInfoMapper = new SignAcctInfoMapperImpl();
        List<SignAcctInfo> signAcctInfoList = signAcctInfoMapper.selectSignAcctInfoByCertInfo(instId, cardSignAcqRequest.getCertType(), cardSignAcqRequest.getCertNo());
        if (null != signAcctInfoList && !signAcctInfoList.isEmpty()) {
            signAcctInfoList = signAcctInfoList.stream().filter(item -> item.getSignType().equals(signType) && item.getSignInst().equals(signInst)).collect(Collectors.toList());
            /* 判断该用户是否已经签约该银行卡 */
            Optional<SignAcctInfo> signAcctInfoOpt = signAcctInfoList.stream().filter(item -> item.getSignAcctNo().equals(payAcctNo) && item.getUserId().equals(cardSignAcqRequest.getUserId())).findFirst();
            if (signAcctInfoOpt.isPresent()) {
                /* 检查姓名是否一致 */
                if (!cardSignAcqRequest.getCardHoldName().equals(signAcctInfoOpt.get().getCardHoldName())) {
                    rglog.info("持卡人姓名不一致,上传持卡人姓名:<{}>,已存在的持卡人姓名:<{}>", cardSignAcqRequest.getCardHoldName(), signAcctInfoOpt.get().getCardHoldName());
                    throw new BizException(RespCodeEnum.CARD_NAME_DIFF.getRespCode(), RespCodeEnum.CARD_NAME_DIFF.getRespDesc());
                }
                /* 银联全渠道签约校验签约账号是否已经签约，bizType取值说明：00-新签约，01-重新签约，02-修改签约预留手机号 */
                signAcctInfoList = signAcctInfoList.stream().filter(item -> AppParamConstant.YES.equals(item.getSignStatus())).collect(Collectors.toList());
                if (!signAcctInfoList.isEmpty() && "00".equals(bizType)) {
                    rglog.info("当前卡:<{}>用户:<{}>已签约且签约状态为:<{}>", cardSignAcqRequest.getPayAcctNo(), userId, signAcctInfoOpt.get().getSignStatus());
                    throw new BizException(RespCodeEnum.EXIT_SIGN_INFO_ERROR.getRespCode(), RespCodeEnum.EXIT_SIGN_INFO_ERROR.getRespDesc());
                }
            } else if ("02".equals(bizType)) {
                rglog.debug("用户修改他行卡预留手机号,校验用户当前卡必须存在签约关系");
                rglog.info("用户:<{}>在当前签约主体:<{}>上无任何签约关系", userId, signInst);
                throw new BizException(RespCodeEnum.INVALID_SIGN.getRespCode(), RespCodeEnum.INVALID_SIGN.getRespDesc());
            }
        }

        /* Step5:基于签约信息登记或者更新签约信息 */
        if (null == signAcctInfoList || signAcctInfoList.isEmpty()) {
            rglog.info("用户不存在签约信息(包括已经解约的签约数据),instId:<{}>,certType:<{}>,certNo:<{}>,signType:<{}>,signInst:<{}>", instId, certType, certNo, signType, signInst);
            /* 登记银联全渠道签约信息 */
            registSignAcctInfo(cardSignAcqRequest, upacMchntInfo, signType, signInst);
        } else {
            SignAcctInfo signAcctInfo = signAcctInfoList.get(0);
            rglog.info("签约信息表中已存在该用户在全渠道无跳转的签约关系，更新签约系统流水号");
            signAcctInfo.setOtherSignData(upacMchntInfo.toJSONString());
            signAcctInfo.setSignTokenInst(upacMchntInfo.getString(OpmsParamConstant.MER_ID));
            /* 非修改绑卡手机号的交易需要将签约状态更新为初始状态-I */
            if (!"02".equals(cardSignAcqRequest.getHeader().getBizType())) {
                rglog.debug("非修改绑卡手机号的交易需要将签约状态更新为初始状态-I,bizType:<{}>", cardSignAcqRequest.getHeader().getBizType());
                signAcctInfo.setSignStatus(OpmsParamConstant.OPMS_PARAM_INIT);
            }
            signAcctInfo.setSignSysSeqNum(sysSeqNum);
            signAcctInfo.setUpdateTime(DateUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss.SSS"));
            if (OpmsParamConstant.INT_ZERO != signAcctInfoMapper.updateSignAcctInfoByPrimaryKey(signAcctInfo)) {
                rglog.info("更新签约信息表系统流水号失败");
                throw new BizException(RespCodeEnum.UPDATE_SIGN_ACCT_ERROR.getRespCode(), RespCodeEnum.UPDATE_SIGN_ACCT_ERROR.getRespDesc());
            }
        }

        /* step6:获取全渠道开通报文 */
        Map<String, String> upacReqMap = genUpacSignMsg(cardSignAcqRequest, upacMchntInfo, userInfo);
        rglog.debug("全渠道请求报文:<{}>", JSON.toJSONString(upacReqMap));
        String upacSecReqMsg = genSecurityMsg(upacReqMap, instId, prodType);
        rglog.debug("无跳转签约请求报文:<{}>", upacSecReqMsg);

        /* step7:组装应答报文 */
        CardSignAcqResponse cardSignAcqResponse = new CardSignAcqResponse();
        cardSignAcqResponse.setGatewayReqMsg(Base64.encodeBase64String(upacSecReqMsg.getBytes()));
        BizResponse<CardSignAcqResponse> bizResponse = new BizResponse<>();
        bizResponse.setRspSysId(AppParamConstant.SYS_ID);
        bizResponse.setRespCode(RespCodeEnum.TRANS_SUCCESS.getRespCode());
        bizResponse.setRespDesc(RespCodeEnum.TRANS_SUCCESS.getRespDesc());
        bizResponse.setResult(cardSignAcqResponse);
        return bizResponse;
    }

    @Override
    protected void bizAfterCust(Object response) throws Exception {
        BizResponse bizResponse = (BizResponse) response;
        CardSignAcqResponse result = (CardSignAcqResponse) bizResponse.getResult();
        ConvertUtil.convertInput(result);
    }


    /**
     * 获取银联全渠道商户信息
     *
     * @param instId      多法人标识
     * @param platMchntNo 平台商户号
     * @param mchntNo     商户号
     * @param signType    签约类型
     * @return 银联全渠道商户相关信息
     * @throws Exception 异常
     */
    private JSONObject getUpacMchntInfo(String instId, String platMchntNo, String mchntNo, String signType) throws Exception {
        String prodType = null;
        String upacMchntNo = null;
        JSONObject jsonObject;
        if (SignTypeEnum.MCHNT_CLASS_SIGN.getSignTypeCode().equals(signType)) {
            if (StringUtil.isEmptyOrNull(mchntNo) || null == mchntInfo) {
                rglog.error("商户级签约签约商户号<{}>未上送或者商户状态异常!", mchntNo);
                throw new BizException(RespCodeEnum.MCHNT_INFO_NOT_FOUND.getRespCode(), RespCodeEnum.MCHNT_INFO_NOT_FOUND.getRespDesc());
            }
            Map<String, Object> mchntMap = JSONObject.parseObject(mchntInfo.getCupsChannelMchntNo());
            for (Map.Entry entry : mchntMap.entrySet()) {
                if (((String) entry.getKey()).startsWith("TK")) {
                    prodType = (String) entry.getKey();
                    upacMchntNo = (String) entry.getValue();
                    break;
                }
            }
        } else {
            if (SignTypeEnum.BANK_CLASS_SIGN.getSignTypeCode().equals(signType) || SignTypeEnum.PLAT_CLASS_SIGN.getSignTypeCode().equals(signType)) {
                if (StringUtil.isEmptyOrNull(platMchntNo) || null == platMchntInfo) {
                    rglog.error("银行级或平台级签约签约平台商户号<{}>未上送或者平台商户状态异常!", platMchntNo);
                    throw new BizException(RespCodeEnum.MCHNT_INFO_NOT_FOUND.getRespCode(), RespCodeEnum.MCHNT_INFO_NOT_FOUND.getRespDesc());
                }
            } else {
                rglog.error("未知的签约类型:<{}>", signType);
                throw new BizException(RespCodeEnum.UNSUPPORT_SIGN_TYPE.getRespCode(), RespCodeEnum.UNSUPPORT_SIGN_TYPE.getRespDesc());
            }
        }
        //从支付通道信息表获取全渠道的 tokenId 和 tokenType
        DstChannelCfgMapper dstChannelCfgMapper = new DstChannelCfgMapperImpl();
        List<DstChannelCfg> dstChannelCfgList = dstChannelCfgMapper.selectDstChannelCfgBydstChannelId(instId, ChannelEnum.UPAC.getChannelType());
        if (null == dstChannelCfgList || dstChannelCfgList.isEmpty()) {
            rglog.error("银联全渠道通道配置(T_P_DST_CHANNEL_CFG)不存在!");
            throw new BizException(RespCodeEnum.SYSTEM_ERROR.getRespCode(), RespCodeEnum.SYSTEM_ERROR.getRespDesc());
        }
        Optional<DstChannelCfg> dstChannelCfgOpt;
        if (!StringUtil.isEmptyOrNull(prodType)) {
            String finalProdType = prodType;
            dstChannelCfgOpt = dstChannelCfgList.stream().filter(item -> item.getDstChannelType().equals(finalProdType)).findFirst();
        } else {
            dstChannelCfgOpt = dstChannelCfgList.stream().filter(item -> item.getDstChannelType().startsWith("TK")).min(Comparator.comparing(DstChannelCfg::getDstChannelType));
        }
        if (!dstChannelCfgOpt.isPresent() || StringUtil.isEmptyOrNull(dstChannelCfgOpt.get().getOtherConfParam())) {
            rglog.error("银联全渠道不存在可用的tokenType和tokenId！");
            throw new BizException(RespCodeEnum.SYSTEM_ERROR.getRespCode(), RespCodeEnum.SYSTEM_ERROR.getRespDesc());
        }
        jsonObject = JSONObject.parseObject(dstChannelCfgOpt.get().getOtherConfParam());
        String trId = jsonObject.getString(OpmsParamConstant.TR_ID);
        String tokenType = jsonObject.getString(OpmsParamConstant.TOKEN_TYPE);
        if (null == trId || null == tokenType) {
            rglog.error("银联全渠道不存在可用的tokenType和tokenId！");
            throw new BizException(RespCodeEnum.SYSTEM_ERROR.getRespCode(), RespCodeEnum.SYSTEM_ERROR.getRespDesc());
        }
        prodType = StringUtil.isEmptyOrNull(prodType) ? dstChannelCfgOpt.get().getDstChannelType() : prodType;
        upacMchntNo = StringUtil.isEmptyOrNull(upacMchntNo) ? dstChannelCfgOpt.get().getProviderMchntNo() : upacMchntNo;
        if (null == upacMchntNo || StringUtil.isEmptyOrNull(prodType)) {
            rglog.error("商户未开通银联业务，不支持银联token支付");
            throw new BizException(RespCodeEnum.NOT_SURP_ACCTTYPE_ERROR.getRespCode(), RespCodeEnum.NOT_SURP_ACCTTYPE_ERROR.getRespDesc());
        }
        jsonObject.put(OpmsParamConstant.PROD_TYPE, prodType);
        jsonObject.put(OpmsParamConstant.MER_ID, upacMchntNo);
        return jsonObject;
    }

    /**
     * 生成全渠道支付开通报文
     *
     * @param request    请求体
     * @param jsonObject 银联全渠道商户相关信息
     * @param userInfo   用户信息
     * @return 请求报文
     * @throws Exception 异常
     */
    private Map<String, String> genUpacSignMsg(CardSignAcqRequest request, JSONObject jsonObject, UserInfo userInfo) throws Exception {
        if (null == jsonObject) {
            jsonObject = new JSONObject();
        }
        //获取全渠道后台通知地址
        String instId = request.getHeader().getInstId();
        SysParamService sysParamService = new SysParamServiceImpl();
        SysParam sysParamInfo = sysParamService.getSysParamInfo(instId, OpmsParamConstant.UPAC_CONF_NODE, OpmsParamConstant.UPAC_BANK_URL);
        if (null == sysParamInfo || AppParamConstant.NO.equals(sysParamInfo.getParamStatus())) {
            rglog.info("机构全渠道后台通知地址未配置,instId:<{}>", instId);
            throw new BizException(RespCodeEnum.SYSTEM_ERROR.getRespCode(), RespCodeEnum.SYSTEM_ERROR.getRespDesc());
        }

        String tokenPayData = "{trId=" + jsonObject.getString(OpmsParamConstant.TR_ID) + "&tokenType=" + jsonObject.getString(OpmsParamConstant.TOKEN_TYPE) + "}";
        XmlTreeUtil xmlTreeUtil = new XmlTreeUtil();
        String sysSeqNum = xmlTreeUtil.getXmlTreeStringValue(TreeNodeConstant.SYS_SEQ_NUM);
        String orderDateTime = sysSeqNum.substring(8, 22);

        Map<String, String> contentData = new HashMap<>();
        //版本号
        contentData.put("version", OpmsUpacSecurityUtil.VERSION_5_1_0);
        //字符集编码 可以使用UTF-8,GBK两种方式
        contentData.put("encoding", OpmsUpacSecurityUtil.UTF_8_ENCODING);
        //签名方法
        contentData.put("signMethod", OpmsUpacSecurityUtil.SIGNMETHOD_RSA);
        //交易类型 11-代收
        contentData.put("txnType", "79");
        //交易子类型 00-默认开通
        contentData.put("txnSubType", "00");
        //业务类型 认证支付2.0
        contentData.put("bizType", "000902");
        //渠道类型07-PC
        contentData.put("channelType", ChannelTypeEnum.MAPP.getDstType());

        //商户接入参数
        contentData.put("merId", jsonObject.getString(OpmsParamConstant.MER_ID));
        /*根据银联商户接入模式上送accessType 0:普通商户接入，1：收单模式接入，2：平台模式接入*/
        String accessType = jsonObject.getString(OpmsParamConstant.ACCESS_TYPE);
        if (StringUtil.isEmptyOrNull(accessType)) {
            rglog.info("默认商户模式接入");
            contentData.put("accessType", "0");
        } else {
            contentData.put("accessType", accessType);
        }
        contentData.put("orderId", sysSeqNum);
        contentData.put("txnTime", orderDateTime);
        //获取敏感信息加密公钥信息
        CertInfo certInfo = getEncryptCertInfo(instId, jsonObject.getString(OpmsParamConstant.PROD_TYPE));
        encryptData(request.getPayAcctNo(), contentData, certInfo);
        contentData.put("tokenPayData", tokenPayData);

        //银行卡验证信息及身份信息
        Map<String, String> customerInfoMap = new HashMap<>();
        customerInfoMap.put("certifTp", request.getCertType());
        customerInfoMap.put("certifId", request.getCertNo());
        customerInfoMap.put("customerNm", request.getCardHoldName());
        //银行预留手机号非空，拼接手机号
        if (!StringUtil.isEmptyOrNull(request.getPhoneNo())) {
            customerInfoMap.put("encryptedInfo", OpmsUpacSecurityUtil.encryptData("phoneNo=" + request.getPhoneNo(), certInfo.getCertValue(), OpmsUpacSecurityUtil.UTF_8_ENCODING));
        }
        String customerInfoStr = OpmsUpacSecurityUtil.covertMap2String(customerInfoMap);
        customerInfoStr = "{" + customerInfoStr + "}";
        contentData.put("customerInfo", new String(Base64.encodeBase64(customerInfoStr.getBytes())));

        //后台通知地址
        contentData.put("backUrl", sysParamInfo.getParamValue() + instId + "/" + OpmsParamConstant.OPM_SYS_ID);
        //前置URL
        contentData.put("frontUrl", request.getFrontBackUrl());
        //订单超时时间
        contentData.put("payTimeout", new SimpleDateFormat("yyyyMMddHHmmss").format(System.currentTimeMillis() + 15 * 60 * 1000));
        //请求方保留域 transType 用于区分是签约还是重新签约 0:签约 1:重新签约
        jsonObject.put(OpmsParamConstant.ACQ_RESERVED_USERID, request.getUserId());
        String isCreditGrant = (null == platMchntInfo || platMchntInfo.getGrantCreditFlag().equals(OpmsParamConstant.STR_ZERO)) ? AppParamConstant.NO : AppParamConstant.YES;
        jsonObject.put("isCreditGrant", isCreditGrant);
        jsonObject.put("userInst", userInfo.getUserInst());
        jsonObject.put("transType", "");
        String bizType = request.getHeader().getBizType();
        if ("00".equals(bizType)) {
            //签约
            jsonObject.put("transType", OpmsParamConstant.STR_ZERO);
        } else if ("01".equals(bizType)) {
            //重新签约
            jsonObject.put("transType", OpmsParamConstant.STR_ONE);
        } else if ("02".equals(bizType)) {
            //修改预留手机号
            jsonObject.put("transType", OpmsParamConstant.STR_ZERO);
        } else {
            rglog.info("交易码传入错误,bizType:<{}>", bizType);
            throw new BizException(RespCodeEnum.PARAM_ERROR.getRespCode(), RespCodeEnum.PARAM_ERROR.getRespDesc());
        }
        //收银台前端需要数据
        jsonObject.put("userId", request.getUserId());
        jsonObject.put("mchntNo", request.getMchntNo());
        jsonObject.put("signOrderId", sysSeqNum);
        jsonObject.put("signSendTime", orderDateTime);
        contentData.put("reqReserved", new String(Base64.encodeBase64(jsonObject.toJSONString().getBytes())));
        return contentData;
    }

    /**
     * 对报文进行加签
     *
     * @param signDataMap 请求内容map
     * @param instId      法人机构号
     * @param prodType    全渠道产品类型
     * @return 请求报文
     * @throws Exception 异常
     */
    private String genSecurityMsg(Map<String, String> signDataMap, String instId, String prodType) throws Exception {
        CertInfo certInfo = getSignCertInfo(instId, prodType);
        signDataMap.put("certId", certInfo.getCertId());

        /* 报文加签 */
        rglog.trace("加签报文的私钥证书值:<{}>", certInfo.getCertValue());
        if (!OpmsUpacSecurityUtil.signBySecureKey(signDataMap, certInfo.getCertValue(), OpmsUpacSecurityUtil.UTF_8_ENCODING)) {
            rglog.error("报文加签失败");
            throw new BizException(RespCodeEnum.GENERATE_SIGN_BLOCK_ERROR.getRespCode(), RespCodeEnum.GENERATE_SIGN_BLOCK_ERROR.getRespDesc());
        }
        SysParamService sysParamService = new SysParamServiceImpl();
        SysParam sysParamInfo = sysParamService.getSysParamInfo(instId, OpmsParamConstant.UPAC_CONF_NODE, OpmsParamConstant.UPAC_FRONT_URL);
        if (null == sysParamInfo) {
            rglog.info("机构全渠道后台通知地址未配置,instId:<{}>", instId);
            throw new BizException(RespCodeEnum.SYSTEM_ERROR.getRespCode(), RespCodeEnum.SYSTEM_ERROR.getRespDesc());
        }
        return OpmsUpacSecurityUtil.createFormHtml(sysParamInfo.getParamValue(), signDataMap, OpmsUpacSecurityUtil.UTF_8_ENCODING);
    }

    /**
     * 登记签约信息表
     *
     * @param cardSignAcqRequest 请求实体
     * @param jsonObject         银联全渠道商户相关信息
     * @param signType           签约类型
     * @param signInst           签约主体
     * @throws Exception 异常
     */
    private void registSignAcctInfo(CardSignAcqRequest cardSignAcqRequest, JSONObject jsonObject, String signType, String signInst) throws Exception {
        //获取卡BIN
        BinInfoServiceImpl binInfoService = new BinInfoServiceImpl();
        BinInfo cardBinInfo = binInfoService.getCardBinInfo(cardSignAcqRequest.getPayAcctNo());
        if (cardBinInfo == null) {
            rglog.info("卡BIN信息不存在，cardNo:<{}>", cardSignAcqRequest.getPayAcctNo());
            throw new BizException(RespCodeEnum.GET_CARD_BIN_INFO_ERROR.getRespCode(), RespCodeEnum.GET_CARD_BIN_INFO_ERROR.getRespDesc());
        }
        //获取发卡机构的联行行号
        BompService bompService = new BompServiceImpl();
        BankCodeInfo bankCodeInfo = bompService.getBankCodeInfoByCupsInstId(cardBinInfo.getIssuerCode(), cardBinInfo.getCardSign());
        if (null == bankCodeInfo) {
            rglog.error("根据银联机构号<{}>获取联行号失败", cardBinInfo.getIssuerCode());
            char[] issCode = cardBinInfo.getIssuerCode().toCharArray();
            if (issCode[0] == '0') {
                issCode[0] = '6';
            } else if (issCode[0] == '6') {
                issCode[0] = '0';
            } else {
                throw new BizException(RespCodeEnum.NOT_VLIAD_INST_INFO_ERROR.getRespCode(), RespCodeEnum.NOT_VLIAD_INST_INFO_ERROR.getRespDesc());
            }
            /* 由于某些行借记卡和贷记卡银联机构号不一样，转换机构号后再获取联行行号信息 */
            bankCodeInfo = bompService.getBankCodeInfoByCupsInstId(new String(issCode), cardBinInfo.getCardSign());
            if (null == bankCodeInfo) {
                rglog.error("根据银联机构号<{}>获取联行号失败", new String(issCode));
                throw new BizException(RespCodeEnum.NOT_VLIAD_INST_INFO_ERROR.getRespCode(), RespCodeEnum.NOT_VLIAD_INST_INFO_ERROR.getRespDesc());
            }
        }
        XmlTreeUtil xmlTreeUtil = new XmlTreeUtil();
        String sysSeqNum = xmlTreeUtil.getXmlTreeStringValue(TreeNodeConstant.SYS_SEQ_NUM);
        SignAcctInfo signAcctInfo = new SignAcctInfo();
        signAcctInfo.setInstId(cardSignAcqRequest.getHeader().getInstId());
        signAcctInfo.setUserId(cardSignAcqRequest.getUserId());
        signAcctInfo.setSignToken(sysSeqNum);
        signAcctInfo.setSignTokenType(SignTokenTypeEnum.UPAC_TOKEN.getSignTypeCode());
        signAcctInfo.setSignType(signType);
        signAcctInfo.setSignInst(signInst);
        signAcctInfo.setCardHoldName(cardSignAcqRequest.getCardHoldName());
        signAcctInfo.setCertType(cardSignAcqRequest.getCertType());
        signAcctInfo.setCertNo(cardSignAcqRequest.getCertNo());
        if ("1".equals(cardBinInfo.getCardType())) {
            signAcctInfo.setSignAcctType(AcctTypeEnum.OTHER_DEBIT.getAcctType());
        } else {
            signAcctInfo.setSignAcctType(AcctTypeEnum.OTHER_CREDIT.getAcctType());
        }
        signAcctInfo.setPayIndex(OpmsParamConstant.INT_ZERO);
        signAcctInfo.setSignAcctNo(cardSignAcqRequest.getPayAcctNo());
        signAcctInfo.setReserveMobileNo(" ");
        signAcctInfo.setSignBankNo(bankCodeInfo.getBankBranchCode());
        signAcctInfo.setSignBankName(bankCodeInfo.getBankBranchName());
        signAcctInfo.setSignDate(cardSignAcqRequest.getHeader().getTransDate());
        signAcctInfo.setSignSysSeqNum(sysSeqNum);
        signAcctInfo.setSignExpiryDate(" ");
        signAcctInfo.setUnsignDate("");
        signAcctInfo.setUnsignSysSeqNum("");
        signAcctInfo.setSignStatus(OpmsParamConstant.OPMS_PARAM_INIT);
        if (null != jsonObject) {
            signAcctInfo.setOtherSignData(jsonObject.toJSONString());
            signAcctInfo.setSignTokenInst(jsonObject.getString(OpmsParamConstant.MER_ID));
        }

        signAcctInfo.setSingleLimitAmt(xmlTreeUtil.getXmlTreeStringValue("defaultSingleMaxLimitAmt"));
        signAcctInfo.setDayLimitAmt(xmlTreeUtil.getXmlTreeStringValue("defaultDayMaxLimitAmt"));
        signAcctInfo.setNoPasswordAmt(xmlTreeUtil.getXmlTreeStringValue("defaultnoPassWdLimitAmt"));
        signAcctInfo.setAuthLimitAmt(xmlTreeUtil.getXmlTreeStringValue("defaultAuthLimitAmt"));
        signAcctInfo.setCreateTime(DateUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss.SSS"));
        SignAcctInfoMapper signAcctInfoMapper = new SignAcctInfoMapperImpl();
        int recode = signAcctInfoMapper.insertSignAcctInfo(signAcctInfo);
        if (OpmsParamConstant.INT_ZERO != recode) {
            rglog.info("插入签约信息表失败");
            throw new BizException(RespCodeEnum.USER_REGISTER_ERROR.getRespCode(), RespCodeEnum.USER_REGISTER_ERROR.getRespDesc());
        }
        rglog.debug("登记签约信息表成功");
    }

    /**
     * 获取敏感信息加密公钥
     *
     * @param prodType 证书保存标识
     * @throws Exception 异常
     */
    protected CertInfo getEncryptCertInfo(String instId, String prodType) throws Exception {
        Optional<SecPlanInfo> secPlanInfoOpt = getSecPlanInfoOpt(instId, prodType, OpmsParamConstant.ENCRYPT_CERT_TYPE);
        if (!secPlanInfoOpt.isPresent()) {
            rglog.error("根据安全计划id<{}>获取安全计划失败");
            throw new BizException(RespCodeEnum.GENERATE_SIGN_ERROR.getRespCode(), RespCodeEnum.GENERATE_SIGN_ERROR.getRespDesc());
        }
        /* 优先从缓存中获取证书对象 */
        String certIndex = instId + secPlanInfoOpt.get().getSecPlanId() + secPlanInfoOpt.get().getSecPlanType();
        CertInfo encryptCertInfo = (CertInfo) UnifiedCache.get(OpmsParamConstant.CERT_KEY_CACHE, certIndex);
        if (encryptCertInfo == null) {
            //缓存中未获取到证书对象,查询证书信息表
            rglog.info("根据证书索引certIndex<{}>,从缓存中获取公钥信息失败,从证书配置表中获取.", certIndex);
            CertInfoMapper certInfoMapper = new CertInfoMapperImpl();
            encryptCertInfo = certInfoMapper.selectCertInfoByPrimaryKey(certIndex);
            if (encryptCertInfo == null) {
                rglog.info("根据根据证书索引certIndex<{}><{}>从证书配置表中获取公钥信息失败", certIndex);
                throw new BizException(RespCodeEnum.GENERATE_SIGN_ERROR.getRespCode(), RespCodeEnum.GENERATE_SIGN_ERROR.getRespDesc());
            }
            rglog.info("根据根据证书索引certIndex<{}>获取公钥信息encryptCertInfo=<{}>", certIndex, JSON.toJSONString(encryptCertInfo));
            if (OpmsParamConstant.STR_TWO.equals(encryptCertInfo.getCertSaveType())) {
                /*从本地证书文件获取*/
                rglog.debug("从本地文件读取公钥信息.");
                X509Certificate certificate = OpmsUpacSecurityUtil.getCertificateFromCertFile(encryptCertInfo.getCertSavePath());
                if (certificate != null && certificate.getPublicKey() != null) {
                    //设置证书内容
                    encryptCertInfo.setCertValue(Base64.encodeBase64String(certificate.getPublicKey().getEncoded()));
                    //设置证书序列号
                    encryptCertInfo.setCertId(certificate.getSerialNumber().toString());
                    //设置证书缓存
                    if (UnifiedCache.set(OpmsParamConstant.CERT_KEY_CACHE, certIndex, encryptCertInfo) != 0) {
                        rglog.error("设置证书缓存索引号{}缓存失败", encryptCertInfo.getCertIndex());
                    }
                    rglog.info("从本地文件读取公钥信息encryptCertInfo=<{}>", JSON.toJSONString(encryptCertInfo));
                }
            } else {
                rglog.error("暂时不支持证书存储方式<{}>", encryptCertInfo.getCertSaveType());
                throw new BizException(RespCodeEnum.GENERATE_SIGN_ERROR.getRespCode(), RespCodeEnum.GENERATE_SIGN_ERROR.getRespDesc());
            }
        } else {
            //从缓存中获取到证书信息
            rglog.info("根据证书索引id<{}>,从缓存中获取公钥信息encryptCertInfo=<{}>", certIndex, JSON.toJSONString(encryptCertInfo));
        }
        return encryptCertInfo;
    }

    /**
     * 获取数字签名私钥证书信息
     *
     * @param prodType 证书保存标识
     * @throws Exception 异常
     */
    protected CertInfo getSignCertInfo(String instId, String prodType) throws Exception {
        //获取安全计划ID
        rglog.info("获取安全计划ID....");
        Optional<SecPlanInfo> secPlanInfoOpt = getSecPlanInfoOpt(instId, prodType, OpmsParamConstant.SIGN_CERT_TYPE);
        if (!secPlanInfoOpt.isPresent()) {
            rglog.error("根据安全计划id<{}>获取安全计划失败");
            throw new BizException(RespCodeEnum.GENERATE_SIGN_ERROR.getRespCode(), RespCodeEnum.GENERATE_SIGN_ERROR.getRespDesc());
        }
        /* 优先从缓存中获取证书对象 */
        String certIndex = instId + secPlanInfoOpt.get().getSecPlanId() + secPlanInfoOpt.get().getSecPlanType();
        CertInfo certInfo = (CertInfo) UnifiedCache.get(OpmsParamConstant.CERT_KEY_CACHE, certIndex);
        if (certInfo == null) {
            //缓存中未获取到证书对象,查询证书信息表
            rglog.info("根据证书索引certIndex<{}>,从缓存中获取数字签名私钥信息失败,从证书配置表中获取.", certIndex);
            CertInfoMapper certInfoMapper = new CertInfoMapperImpl();
            certInfo = certInfoMapper.selectCertInfoByPrimaryKey(certIndex);
            if (certInfo == null) {
                rglog.info("根据根据证书索引certIndex<{}>从证书配置表中获取数字签名私钥信息失败", certIndex);
                throw new BizException(RespCodeEnum.GENERATE_SIGN_ERROR.getRespCode(), RespCodeEnum.GENERATE_SIGN_ERROR.getRespDesc());
            }
            rglog.info("根据根据证书索引certIndex<{}>获取获取数字签名私钥信息certInfo=<{}>", certIndex, JSON.toJSONString(certInfo));
            if (OpmsParamConstant.STR_TWO.equals(certInfo.getCertSaveType())) {
                /*从本地证书文件获取*/
                KeyStore keyStore = OpmsUpacSecurityUtil.getCertificateFromPfxFile(certInfo.getCertSavePath(), certInfo.getCertPassword());
                makeCertInfo(keyStore, certInfo);
                if (UnifiedCache.set(OpmsParamConstant.CERT_KEY_CACHE, certInfo.getCertIndex(), certInfo) != 0) {
                    rglog.error("设置证书缓存索引号{}缓存失败", certInfo.getCertIndex());
                }
            } else {
                rglog.error("暂时不支持证书存储方式<{}>", certInfo.getCertSaveType());
                throw new BizException(RespCodeEnum.GENERATE_SIGN_ERROR.getRespCode(), RespCodeEnum.GENERATE_SIGN_ERROR.getRespDesc());
            }
        } else {
            //从缓存中获取到证书信息
            rglog.info("根据证书索引id<{}>,从缓存中获取数字签名私钥信息certInfo=<{}>", certIndex, JSON.toJSONString(certInfo));
        }
        return certInfo;
    }

    /**
     * 获取安全计划
     *
     * @param instId   多法人标识
     * @param prodType 通道类型
     * @return 安全计划
     * @throws Exception 异常
     */
    protected Optional<SecPlanInfo> getSecPlanInfoOpt(String instId, String prodType, String secPlanType) throws Exception {
        //获取安全计划ID
        rglog.info("获取安全计划ID....");
        DstChannelCfgMapper dstChannelCfgMapper = new DstChannelCfgMapperImpl();
        DstChannelCfg dstChannelCfg = dstChannelCfgMapper.selectDstChannelCfgByPrimaryKey(instId, ChannelEnum.UPAC.getChannelType(), prodType);
        if (null == dstChannelCfg || StringUtil.isEmptyOrNull(dstChannelCfg.getSecPlanId())) {
            rglog.error("支付通道信息表未配置安全计划ID");
            throw new BizException(RespCodeEnum.SYSTEM_ERROR.getRespCode(), RespCodeEnum.SYSTEM_ERROR.getRespDesc());
        }

        /* 根据通道安全计划获取安全计划 */
        rglog.info("根据通道安全计划获取安全计划....");
        SecPlanInfoMapper secPlanInfoMapper = new SecPlanInfoMapperImpl();
        List<SecPlanInfo> secPlanInfoList = secPlanInfoMapper.selectSecPlanInfoBySecPlanId(instId, dstChannelCfg.getSecPlanId());
        if (null == secPlanInfoList || secPlanInfoList.isEmpty()) {
            rglog.error("根据通道安全计划获取安全计划失败!", dstChannelCfg.getSecPlanId());
            throw new BizException(RespCodeEnum.SEC_PLAN_NOEXIT.getRespCode(), RespCodeEnum.SEC_PLAN_NOEXIT.getRespDesc());
        }
        /* 加签证书 */
        return secPlanInfoList.stream().filter(item -> item.getSecPlanType().equals(secPlanType)).findFirst();
    }


    /**
     * 获取签名私钥证书certId和证书信息
     *
     * @return 证书的物理编号
     */
    public void makeCertInfo(KeyStore keyStore, CertInfo certInfo) {
        try {
            Enumeration<String> aliasenum = keyStore.aliases();
            String keyAlias = null;
            if (aliasenum.hasMoreElements()) {
                keyAlias = aliasenum.nextElement();
            }
            X509Certificate cert = (X509Certificate) keyStore
                    .getCertificate(keyAlias);
            String certId = cert.getSerialNumber().toString();
            certInfo.setCertId(certId);
            RSAPrivateKey priKey = (RSAPrivateKey) keyStore.getKey(keyAlias, certInfo.getCertPassword().toCharArray());
            certInfo.setCertValue(Base64.encodeBase64String(priKey.getEncoded()));
        } catch (Exception e) {
            throw new BizException(RespCodeEnum.CERT_LOAD_INVALID.getRespCode(), RespCodeEnum.CERT_LOAD_INVALID.getRespDesc());
        }
    }

    /**
     * 敏感信息加密处理
     *
     * @param encData 加密数据
     * @throws Exception 异常
     */
    protected String encryptData(String encData, Map<String, String> signDataMap, CertInfo encryptCertInfo) throws Exception {
        //设置敏感信息加密证书序列编号
        signDataMap.put("encryptCertId", encryptCertInfo.getCertId());
        //敏感信息加密
        rglog.info("对敏感字段进行加密....encData:<{}>,公钥为<{}>", encData, encryptCertInfo.getCertValue());
        String encAcctNo = OpmsUpacSecurityUtil.encryptData(encData, encryptCertInfo.getCertValue(), OpmsUpacSecurityUtil.UTF_8_ENCODING);
        signDataMap.put("accNo", encAcctNo);
        return encAcctNo;
    }
}
